﻿using System;
using System.Threading.Tasks;
using Yto.Common;
using Yto.Common.Extensions;

namespace Yto.Unitwork
{
    public abstract class UnitworkBase : IUnitwork
    {
        public UnitworkBase(IConnStringResolver connStringResolver)
        {
            Id = Guid.NewGuid().ToString("N");
            _connStringResolver = connStringResolver;
        }

        public UnitworkOptions _options { get; private set; }

        public string Id { get; private set; }

        protected IConnStringResolver _connStringResolver;

        public event EventHandler Disposed;
        public event EventHandler Completed;
        public event EventHandler<FailedEventArgs> Failed;


        public void Begin(UnitworkOptions options)
        {
            _options = options;
            BeginUnit();
        }

        protected abstract void BeginUnit();

        private bool isCompleted;
        private bool isSucceeded;
        private Exception exception;

        public void Complete()
        {
            if (isCompleted)
            {
                throw new BaseException("Complete was called!");
            }
            isCompleted = true;

            try
            {
                CompleteUnit();
                isSucceeded = true;
                OnCompleted();
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
        }

        protected virtual void OnCompleted()
        {
            Completed.InvokeEvent(this);
        }
        protected abstract void CompleteUnit();

        public async Task CompleteAsync()
        {
            if (isCompleted)
            {
                throw new BaseException("Complete was called!");
            }
            isCompleted = true;

            try
            {
                await CompleteUnitAsync();
                isSucceeded = true;
                OnCompleted();
            }
            catch (Exception ex)
            {
                exception = ex;
                throw;
            }
        }

        protected abstract Task CompleteUnitAsync();

        public bool IsDisposed { get; private set; }

        public void Dispose()
        {
            if (!isCompleted || IsDisposed)
            {
                return;
            }

            IsDisposed = true;
            if (!isSucceeded)
            {
                OnFailed(exception);
            }
            DisposeUnit();
            OnDisposed();
        }

        protected virtual void OnFailed(Exception ex)
        {
            Failed.InvokeEvent(this, new FailedEventArgs(ex));
        }

        protected virtual void OnDisposed()
        {
            Disposed.InvokeEvent(this);
        }
        protected abstract void DisposeUnit();


        protected abstract void SaveChanges();

        protected abstract Task SaveChangesAsync();
    }
}
